﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MS.Internal.Xml.XPath;
using RW.Data;
using Action = RW.Data.Objects.Action;
using RW.Data.Objects;

namespace RW.Parser
{
    public static class Parser
    {
        private static List<string> LogicOperators = new List<string>() { "and", "or", "=>", "<=>" };

        public static Problem Parse(string fileName)
        {
            List<string> stateNames = new List<string>() { "singing", "alive", "loaded" };
            List<string> actionNames = new List<string>() { "SHOOT", "RUN", "LOAD" };

            Problem problem;

            using (StreamReader reader = new StreamReader(fileName))
            {
                string line = reader.ReadLine();

                if (line.StartsWith("variables:", StringComparison.InvariantCultureIgnoreCase))
                {
                    stateNames = line.Substring(line.IndexOf(':') + 1).SplitAndTrim(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                    line = reader.ReadLine();
                }

                if (line.StartsWith("actions:", StringComparison.InvariantCultureIgnoreCase))
                {
                    actionNames = line.Substring(line.IndexOf(':') + 1).SplitAndTrim(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                    line = reader.ReadLine();
                }

                problem = new Problem(stateNames, actionNames.Select(a => new Action(a)).ToList());

                while (line != null)
                {
                    CheckLine(line, problem);

                    try
                    {
                        if (line.Contains(KeyWords.After.ToString().ToLower()))
                            problem.Observations.Add(ParseObservation(line, problem.Actions));
                        else if (line.StartsWith(KeyWords.Initially.ToString().ToLower()))
                            problem.InitialState = new LogicStatement(line.Substring(10));
                        else if (line.Contains(KeyWords.Causes.ToString().ToLower()))
                            ParseActionResult(line, problem.Actions);
                        else if (line.StartsWith(KeyWords.Impossible.ToString().ToLower()))
                            ParseImpossibleCondition(line, problem.Actions);
                        else if (line.Contains(KeyWords.Releases.ToString().ToLower()))
                            ParseActionRelease(line, problem.Actions);
                        else if (line.StartsWith(KeyWords.Always.ToString().ToLower()))
                            problem.IntegrityConditions.Add(ParseIntegrityCondition(line));
                        else if (line.Contains(KeyWords.Costs.ToString().ToLower()))
                            ParseActionCost(line, problem.Actions);
                    }
                    catch (Exception e)
                    {
                        throw new Exception(String.Format("Line: '{0}' is invalid. Exception: {1}", line, e.Message));
                    }

                    line = reader.ReadLine();
                }
            }

            return problem;
        }

        private static void CheckLine(string line, Problem problem)
        {
            string[] split = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (string word in split)
            {
                string trimedWord = word.TrimStart('~').TrimEnd(',');

                int outInt;
                if (Int32.TryParse(trimedWord, out outInt))
                    continue;

                if (!problem.Variables.Contains(trimedWord, StringComparer.InvariantCultureIgnoreCase)
                    && !problem.Actions.Select(a => a.Name).Contains(trimedWord, StringComparer.InvariantCultureIgnoreCase)
                    && !Enum.GetNames(typeof(KeyWords)).Contains(trimedWord, StringComparer.InvariantCultureIgnoreCase)
                    && !LogicOperators.Contains(trimedWord, StringComparer.InvariantCultureIgnoreCase))
                    throw new Exception(String.Format("Word: {0} is invalid. It does not match any variable, action or KeyWord. Check if spelling is correct.", trimedWord));
            }
        }

        private static ActionCost ParseActionCost(string line, List<Action> actions)
        {
            string[] split = line.SplitAndTrim(new[] { KeyWords.Costs.ToString().ToLower(), KeyWords.If.ToString().ToLower() }, StringSplitOptions.RemoveEmptyEntries);

            ActionCost result = new ActionCost();
            result.Cost = Int32.Parse(split[1]);

            if (split.Count() == 3)
                result.Conditions = new LogicStatement(split[2]);
            else
                result.Conditions = new LogicStatement(null);

            actions.Single(a => a.Name == split[0]).Costs.Add(result);
            return result;
        }

        private static IntegityCondition ParseIntegrityCondition(string line)
        {
            string[] split = line.SplitAndTrim(new[] { KeyWords.Always.ToString().ToLower(), KeyWords.If.ToString().ToLower() }, StringSplitOptions.RemoveEmptyEntries);

            IntegityCondition result = new IntegityCondition();
            result.Condition = new LogicStatement(split[0]);

            if (split.Count() == 2)
                result.IfCondition = new LogicStatement(split[1]);
            else
                result.IfCondition = new LogicStatement(null);

            return result;
        }

        private static ActionRelease ParseActionRelease(string line, List<Action> actions)
        {
            string[] split = line.SplitAndTrim(new[] { KeyWords.Releases.ToString().ToLower(), KeyWords.If.ToString().ToLower() }, StringSplitOptions.RemoveEmptyEntries);

            ActionRelease release = new ActionRelease();
            release.Result = ParseVariables(split[1]).Select(v => v.Key).ToList();

            if (split.Count() == 3)
                release.Conditions = new LogicStatement(split[2]);
            else
                release.Conditions = new LogicStatement(null);

            actions.Single(a => a.Name == split[0]).Released.Add(release);
            return release;
        }

        private static LogicStatement ParseImpossibleCondition(string line, List<Action> actions)
        {
            string[] split = line.SplitAndTrim(new[] { KeyWords.Impossible.ToString().ToLower(), KeyWords.If.ToString().ToLower() }, StringSplitOptions.RemoveEmptyEntries);

            LogicStatement statement = new LogicStatement(split[1]);
            actions.Single(a => a.Name == split[0]).ImpossibleCondtions.Add(statement);

            return statement;
        }

        private static ActionResult ParseActionResult(string line, List<Action> actions)
        {
            string[] split = line.SplitAndTrim(new[] { KeyWords.Causes.ToString().ToLower(), KeyWords.If.ToString().ToLower() }, StringSplitOptions.RemoveEmptyEntries);

            ActionResult result = new ActionResult();
            result.ResultsCondition = new LogicStatement(split[1]);

            if (split.Count() == 3)
                result.Conditions = new LogicStatement(split[2]);
            else
                result.Conditions = new LogicStatement(null);

            actions.Single(a => a.Name == split[0]).Result.Add(result);
            return result;
        }

        private static Observation ParseObservation(string line, List<Action> avaliableActions)
        {
            string[] split = line.SplitAndTrim(new[] { KeyWords.Observable.ToString().ToLower(), KeyWords.After.ToString().ToLower() }, StringSplitOptions.RemoveEmptyEntries);

            Observation observation = new Observation();
            observation.Result = new LogicStatement(split[0]);
            observation.AfterActions = ParseActions(split[1], avaliableActions);
            observation.IsObservable = line.StartsWith(KeyWords.Observable.ToString(), StringComparison.InvariantCultureIgnoreCase);

            return observation;
        }

        private static Dictionary<string, bool> ParseVariables(string line)
        {
            Dictionary<string, bool> result = new Dictionary<string, bool>();
            string[] variables = line.SplitAndTrim(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (string variable in variables)
                result.Add(variable.TrimStart('~'), !variable.StartsWith("~"));

            return result;
        }

        private static List<Action> ParseActions(string line, List<Action> avaliableActions)
        {
            List<Action> result = new List<Action>();
            string[] actions = line.SplitAndTrim(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (string action in actions)
                result.Add(avaliableActions.Single(a => a.Name == action));

            return result;
        }
    }
}
